﻿using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.WebControls;
using System.Reflection;
using System.Collections.Generic;

namespace Yappi.HierarchicalField
{
    // TODO: Replace, as needed, "SPFieldText" with some other class derived from SPField. 
    // TODO: Update, as needed, ParentType element in fldtypes*.xml in this solution. 
    [CLSCompliant(false)]
    [Guid("48a9d327-e0c8-4c62-a018-ac321b111cbd")]
    public class HierarchicalField : SPFieldLookup
    {
        public HierarchicalField(SPFieldCollection fields, string fieldName)
            : base(fields, fieldName)
        {
        }
        
        public HierarchicalField(SPFieldCollection fields, string typeName, string displayName)
            : base(fields, typeName, displayName)
        {
        }

        #region Настройки для поля

        /// <summary>
        /// Адрес узла списка подстановки
        /// </summary>
        public string LookupWebUrl
        {
            get { return this.GetFieldAttribute("LookupWebUrl") + string.Empty; }
            set { this.SetFieldAttribute("LookupWebUrl",value); }
        }

        /// <summary> 
        /// Имя списка подстановки
        /// </summary>        
        public string LookupListName
        {
            get { return this.GetFieldAttribute("LookupListName") + string.Empty; }
            set { this.SetFieldAttribute("LookupListName", value); }
        }
        /// <summary>
        /// Поле подстановки (полное имя)
        /// </summary>
        public string LookupFieldFullName
        {
            get { return this.GetFieldAttribute("LookupFieldFullName") + string.Empty; }
            set { this.SetFieldAttribute("LookupFieldFullName",value); }
        }
        /// <summary>
        /// Поле сортировки
        /// </summary>
        public string LookupFieldShortName
        {
            get { return this.GetFieldAttribute("LookupFieldShortName") + string.Empty; }
            set { this.SetFieldAttribute("LookupFieldShortName", value); }
        }
        /// <summary>
        /// Имя колонки для фильтрации выпадающего списка
        /// </summary>
        public string LookupFieldFilter
        {
            get { return this.GetFieldAttribute("LookupFieldFilter") + string.Empty; }
            set { this.SetFieldAttribute("LookupFieldFilter", value); }

        }
        /// <summary>
        /// Имя колонки с порядком сортировки значений
        /// </summary>
        public string LookupFieldOrder
        {
            get { return this.GetFieldAttribute("LookupFieldOrder") + string.Empty; }
            set { this.SetFieldAttribute("LookupFieldOrder",value);}
        }
        /// <summary>
        /// Значение фильтра
        /// </summary>
        public string FilterValue
        {
            get { return this.GetFieldAttribute("FilterValue") + string.Empty; }
            set { this.SetFieldAttribute("FilterValue", value); }
        }
        /// <summary>Адрес узла, на котором находится список с фильтрующим значением</summary>
        public string FilterWebUrl
        {
            get { return this.GetFieldAttribute("FilterWebUrl") + string.Empty; }
            set { this.SetFieldAttribute("FilterWebUrl", value); }
        }
        /// <summary>Имя списка фильтра</summary>
        public string FilterListName
        {
            get { return this.GetFieldAttribute("FilterListName") + string.Empty; }
            set { this.SetFieldAttribute("FilterListName", value); }
        }
        public string JavaScriptPlacer 
        {
            get { return this.GetFieldAttribute("JavaScriptPlacer") + string.Empty; }
            set { this.SetFieldAttribute("JavaScriptPlacer", value); }
        }
        /// <summary>
        /// Имя столбца, в котором заданы фильтрующие значения
        /// </summary>
        public string FilterValuesFieldName
        {
            get { return this.GetFieldAttribute("FilterValuesFieldName") + string.Empty; }
            set { this.SetFieldAttribute("FilterValuesFieldName", value); }
        }
        /// <summary>Номер строки для значений фильтра</summary>
        public string FilterValuesRowNumber
        {
            get { return this.GetFieldAttribute("FilterValuesRowNumber") + string.Empty; }
            set { this.SetFieldAttribute("FilterValuesRowNumber", value); }
        }
        #endregion


        private bool SkipFiltering = false;
      
        /// <summary>
        /// Добавление  контролов при инициализации нового столбца.
        /// </summary>
        /// <param name="op"></param>
        public override void OnAdded(SPAddFieldOptions op)
        {
            base.OnAdded(op);
            Update();
        }
         
        public override void Update()
        {
            SPSecurity.RunWithElevatedPrivileges(delegate
            {
                using (SPSite site = new SPSite(GetAbsoluteUrl(LookupWebUrl)))                
                using (SPWeb web = site.OpenWeb())
                {
                    SetFieldAttribute("WebId", web.ID.ToString());
                    this.LookupList = web.Lists[this.LookupListName].ID.ToString();
                    this.LookupField = web.Lists[this.LookupListName].Fields[LookupFieldShortName].InternalName;                    
                }                
            }
            );           

            base.Update();            
        }

        /// <summary>
        /// Установка аттрибутов
        /// </summary>
        /// <param name="attribute"></param>
        /// <param name="value"></param>
        private void SetFieldAttribute(string attribute, string value)
        {
            //Hack: Invokes an internal method from the base class
            Type baseType = typeof(HierarchicalField);
            MethodInfo mi = baseType.GetMethod("SetFieldAttributeValue", BindingFlags.Instance | BindingFlags.NonPublic);
            mi.Invoke(this, new object[] { attribute, value });
        }

        private string GetFieldAttribute(string attribute)
        {                        
                //Hack: Invokes an internal method from the base class
            Type baseType = typeof(HierarchicalField);
                MethodInfo mi = baseType.GetMethod("GetFieldAttributeValue", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(String) }, null);
                object obj = mi.Invoke(this, new object[] { attribute });
                if (obj==null)
                    obj = this.GetCustomProperty(attribute);
                if (obj == null)
                    return string.Empty;
                else
                    return obj.ToString();            
        }

        //private bool ContaintsAttribute(string attribute)
        //{
        //    XmlDocument doc = new XmlDocument();
        //    doc.LoadXml(this.SchemaXml);
        //    foreach (XmlAttribute item in doc.DocumentElement.Attributes)
        //    {
        //        if (item.Name == attribute)
        //            return true;
        //    }
        //    return false;
        //}
      
      
        /// <summary>
        /// Основная машиненрия, изменяем поведения поля.
        /// </summary>
        public override BaseFieldControl FieldRenderingControl
        {
            [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
            get
            {
                /* Не используем базовый контрол, для того что бы избежать сброса  значений фильтра  при запросе */
                BaseFieldControl control = null;
                if (!this.CountRelated)
                {
                    if (!this.AllowMultipleValues)
                    {

                        HierarchicalFieldFieldControl customLookupField = new HierarchicalFieldFieldControl();
                        //customLookupField.LookupShortName = this.LookupFieldShortName;

                        control = customLookupField;

                          using(SPSite site = new SPSite(GetAbsoluteUrl(LookupWebUrl)))
                          using (SPWeb lookupWeb = site.OpenWeb())
                          {
                              SPList lookupList = lookupWeb.Lists[LookupListName];
                              string orderByFieldNameInternal;                              
                              string lookupFieldFullNameInternal = lookupList.Fields[LookupFieldFullName].InternalName;                              
                             
                        customLookupField.ValidIds = GetValidItemIds(customLookupField);
                                customLookupField.UseFiltering = true;
                                customLookupField.LookupFullName = lookupFieldFullNameInternal;
                                if (string.IsNullOrEmpty(LookupFieldOrder))
                                    orderByFieldNameInternal = null;
                                else
                                    orderByFieldNameInternal = lookupList.Fields[LookupFieldOrder].InternalName;
                              customLookupField.SortFieldName = orderByFieldNameInternal;
                            
                          }
                    }
                    else
                    {
                        control = new MultipleLookupField();
                    }
                }
                else
                {
                    control = new ComputedField();
                }
                control.FieldName = base.InternalName;                                
                return control;
            }
        }        
        /// <summary>
        /// Получить идентификаторы доступных элементов
        /// </summary>
        /// <returns></returns>
        private int[] GetValidItemIds(HierarchicalFieldFieldControl control)
        {
            SkipFiltering = false;
            List<int> validItems = new List<int>();
            // ДОбавляем пустое поле для первого элемента
            // validItems.Add(0);
            // Получение данных из списка настроек
            if (!string.IsNullOrEmpty(this.FilterValue))
            {
                using(SPSite site = new SPSite(GetAbsoluteUrl(LookupWebUrl)))
                using (SPWeb lookupWeb = site.OpenWeb())
                {
                    SPList lookupList = lookupWeb.Lists[LookupListName];
                    string orderByFieldNameInternal = null;
                    if (!string.IsNullOrEmpty(LookupFieldOrder))
                        orderByFieldNameInternal = lookupList.Fields[LookupFieldOrder].InternalName;
                    else
                        orderByFieldNameInternal = "Title";
                    string lookupFieldFullNameInternal = lookupList.Fields[LookupFieldFullName].InternalName;
                    string lookupFieldFilterNameInternal = lookupList.Fields[LookupFieldFilter].InternalName;
                    string fl = FilterValue;
                    if (FilterValue.StartsWith("=[") && FilterValue.EndsWith("]"))
                    {
                       fl = GetValueFromLook(lookupList, FilterValue, control);
                       if( string.IsNullOrEmpty(fl))
                           SkipFiltering = true;
                    }
                    else
                        fl = FilterValue;
                    validItems.AddRange(GetValidDataItemsByFilterValues(lookupList, lookupFieldFullNameInternal, lookupFieldFilterNameInternal, orderByFieldNameInternal, fl));
                }
            }
            // Получение из значения фильтрации из строки параметров
            else
                validItems.AddRange(GetElementsBySettingList());


            return validItems.ToArray();
        }
        /// <summary>
        /// Обработка строки фильтрации вида =[имя поля]
        /// </summary>
        /// <returns></returns>
        private string GetValueFromLook(SPList lookupList,string filter,BaseFieldControl control)
        {            
            string fieldName = filter.Substring(2, filter.Length - 3);
            SPListItem it =  control.ListItem;
            SPField dataItemNameField =  it.Fields[fieldName];
            return dataItemNameField.GetFieldValueAsText(it[fieldName]);            
        }
        /// <summary>
        /// Получение идентификаторов на основе списка фильтрации.
        /// </summary>
        /// <returns></returns>
        private int[] GetElementsBySettingList()
        {

            using(SPSite lookupSite = new SPSite(GetAbsoluteUrl(LookupWebUrl)))
            using (SPWeb lookupWeb = lookupSite.OpenWeb())
            {
                using(SPSite filterSite = new SPSite(GetAbsoluteUrl(FilterWebUrl)))
                using (SPWeb filterWeb = filterSite.OpenWeb())
                {

                    SPList lookupList = lookupWeb.Lists[LookupListName];
                        string orderByFieldNameInternal = lookupList.Fields[LookupFieldOrder].InternalName;                        
                        string lookupFieldFullNameInternal = lookupList.Fields[LookupFieldFullName].InternalName;
                        string lookupFieldFilterNameInternal = lookupList.Fields[LookupFieldFilter].InternalName;
                        string lookupFieldFilterInternal = lookupList.Fields[LookupFieldFilter].InternalName;
                        //string lookupFieldFullNameInternal = lookupList.Fields[LookupFieldFullName].InternalName;
                        /*
                         Возможно 3 случая фильтрации
                         1 - Нет фильтрации список отдаеться как есть :)
                         2 - Установлен точный ID Записи тогда идет фильтрация по этой строке
                         3 - Установлен специальный режим все значения.
                         */
                    // Проверяем есть ли вообще список и установлено ли поле фильтрации
                    if (!string.IsNullOrEmpty(FilterListName) &&
                        !string.IsNullOrEmpty(FilterValuesFieldName))
                    {

                        SPList filterList = filterWeb.Lists[FilterListName];

                        string filterValuesFieldNameInternal = filterList.Fields[FilterValuesFieldName].InternalName;
                        

                        // 2 - Установлен точный ID Записи тогда идет фильтрация по этой строке
                        if (!string.IsNullOrEmpty(FilterValuesRowNumber)
                            && FilterValuesRowNumber.ToLower() != "[все]")
                        {
                            int id;
                            id = int.Parse(FilterValuesRowNumber);

                            SPListItem li = filterList.GetItemById(id);
                            SPField fieldValueField = filterList.Fields.GetFieldByInternalName(filterValuesFieldNameInternal);
                            string filterString =  fieldValueField.GetFieldValueAsText(li[fieldValueField.Id]);


                            return GetValidDataItemsByFilterValues(lookupList, lookupFieldFullNameInternal,
                                lookupFieldFilterInternal, orderByFieldNameInternal , filterString);
                        }
                        else if (!string.IsNullOrEmpty(FilterValuesRowNumber)
                            && FilterValuesRowNumber.ToLower() == "[все]")
                        {
                            SPField fieldValueField = filterList.Fields.GetFieldByInternalName(filterValuesFieldNameInternal);
                            List<string> v = new List<string>();
                            foreach (SPListItem it in filterList.Items) 
                            {
                                v.Add(fieldValueField.GetFieldValueAsText(it[fieldValueField.Id]));
                            }
                            string[]  values = v.ToArray();
                            return GetValidDataItemsByFilterValues(lookupList, lookupFieldFullNameInternal,
                                lookupFieldFilterInternal, orderByFieldNameInternal, values);
                        }

                    }
                    // 1 - Нет фильтрации список подстановки отдается как есть :)
                    return GetValidDataItemsByFilterValues(lookupList, lookupFieldFullNameInternal,
                                lookupFieldFilterInternal, orderByFieldNameInternal);
                }
            }
            
        }
        
        /// <summary>
        /// Получение валидных элементов из списка подстановки в 
        /// соответствии с запросом и применением сортировки.
        /// </summary>        
        /// <param name="query">Запрос к списку</param>
        /// <returns></returns>
        private int[] GetDataItemsFromListByQuery(SPList list, string query, string dataItemFieldNameInternal)
        {
            List<int> valid = new List<int>();
            SPQuery q = new SPQuery();
            q.ViewFields = "ID";
            q.Query = query;

            SPField dataItemNameField = list.Fields.GetFieldByInternalName(dataItemFieldNameInternal);

            SPListItemCollection li = list.GetItems(q);
            foreach (SPListItem listItem in li)
            {
                valid.Add(listItem.ID);
            }
            return valid.ToArray();

        }
        /// <summary>
        /// Получение элементов с использованием значений фильтра.
        /// </summary>
        /// <returns></returns>
        private int[] GetValidDataItemsByFilterValues(SPList list, string valuesFieldNameInternal, string valuesFieldFilterNameInternal, string orderByFieldNameInternal, params string[] values)
        {
            string whereFormat = "<Where>{0}</Where>";
            string orFormat = "<Or>{0}{1}</Or>";
            string equalFormat = @"<Eq>
     <FieldRef Name='{0}' />
     <Value Type='Text'>{1}</Value>
  </Eq>";
            string orderBy = string.Format(@"<OrderBy>
  <FieldRef Name='{0}' Ascending='True' />
</OrderBy>", orderByFieldNameInternal);

            string query = String.Empty;
            //string[] values = new string { "Группа А", "Группа Б" }; //сформировать на основе списка "настроек"
            if (values.Length > 0)
            {
                query = string.Format(equalFormat, valuesFieldFilterNameInternal, values[0]);
                for (int i = 1; i < values.Length; i++)
                {
                    query = string.Format(orFormat, query, string.Format(equalFormat, valuesFieldFilterNameInternal, values[i]));
                }
                query = string.Format(whereFormat, query);
            }
            if(SkipFiltering)
                query = orderBy;
            else
                query += orderBy;

            return GetDataItemsFromListByQuery(list, query, valuesFieldNameInternal);
        }
        

        /// <summary>
        /// Парсер для определения узла.
        /// <remarks>
        /// Работает только в котексте SharePoint
        /// </remarks>
        /// </summary>
        /// <param name="web">Узел который мы хотим открыть</param>
        /// <returns></returns>        
        public static string GetAbsoluteUrl(string url)
        {
            if (string.IsNullOrEmpty(url))
                return SPContext.Current.Web.Url;
            Uri resultUrl = new Uri(new Uri(SPContext.Current.Web.Url), url);
            return resultUrl.AbsoluteUri;
        }
    }
}
